///
/// Newromancy
///
/// V009 - EyeSwitches & Pots & Ultrasonic & Blue Switch & Shell Button via Interrup liefern Werte
///
#include <MIDI.h>
/* common variables */


#include <Wire.h>

#include "Common.h"
#include "LedUtils.h"
#include "ProgramHandler.h"
#include "BlueSwitch.h"
#include "EyeSwitches.h"
#include "HeadPots.h"
#include "UltrasonicReceiver.h"
#include "Shellbuttons.h"

namespace globals {

  ///
  /// during the start of loop() a timesnapshot is taken. During the loop allways the same time is used.
  ///
  volatile unsigned long CurrentLoopTimeInMs;

  volatile byte CurrentState = STATE_MUSIC_MODE;
};

///
/// Mapping Array für die LEDs
///
byte LedArray[5] = {
  LED_VIOLETT,
  LED_BLUE,
  LED_YELLOW,
  LED_ORANGE,
  LED_RED
};

/* --- blue switch handling --- */

///
/// State variable of blue switch.
/// Initialized in BlueSwitchInit()
/// @see BlueSwitchInit
///
volatile static byte BlueSwitchCurrentState;
volatile static byte BlueSwitchNextState;

///
/// Debounce time measurement. If no change processing is ongoing this variable is set to 0
///
volatile static unsigned long BlueSwitchTimeOfLastStateChange;

///
/// @param outBlueSwitchChanged returns true when the state of the blue
///            BlueSwitch was changed
///
/// @return
///   BLUE_SWITH_SPOT | BLUE_SWITCH_NOTE | BLUE_SWITCH_FACE
///
inline byte BlueSwitchReadPosition()
{
  return 
    (digitalRead(PIN_BLUE_SWITCH_RIGHT_NOTE) << 1) 
    + digitalRead(PIN_BLUE_SWITCH_LEFT_FACE);
}

///
/// Initialize the ports used for the blue slider on the shell top
///
inline void BlueSwitchInit() 
{
  pinMode( PIN_BLUE_SWITCH_LEFT_FACE, INPUT );
  pinMode( PIN_BLUE_SWITCH_RIGHT_NOTE, INPUT );

  // initialize global state variable
  BlueSwitchCurrentState = BlueSwitchNextState = BlueSwitchReadPosition();
  BlueSwitchTimeOfLastStateChange = 0;
  // blue_switch_has_changed == true, but we ignore the result.
}

///
/// Blue switch was changed in the last loop, we must do something
///
void BlueSwitchHandleStateChange() 
{
  BlueSwitchNextState = BlueSwitchReadPosition();

  if (    BlueSwitchTimeOfLastStateChange == 0 
    && BlueSwitchNextState != BlueSwitchCurrentState) {
    BlueSwitchTimeOfLastStateChange = globals::CurrentLoopTimeInMs;
    return;
  }

  if ( BlueSwitchTimeOfLastStateChange == 0  ) {
    return; // nothing has changed, so do nothing
  }

  if ((globals::CurrentLoopTimeInMs - BlueSwitchTimeOfLastStateChange) < 10) {
    return;  // we're in the debounceing phase
  }

  // reset debouncing 
  BlueSwitchTimeOfLastStateChange = 0;

  // if after 10ms value is stable, we detect a state change
  BlueSwitchCurrentState = BlueSwitchNextState;

  switch(BlueSwitchCurrentState) {

  case BLUE_SWITCH_NOTE:
    globals::CurrentState = STATE_MUSIC_MODE;  
    DEBUGLN1( "Position: Middle (SPOT) CurrentState = STATE_MUSIC_MODE" ); // auch bei default
    break;

  case BLUE_SWITCH_FACE:
    DEBUGLN1( "Position: Right (FACE) CurrentState = STATE_PROGRAMMING_MODE" );
    globals::CurrentState = STATE_PROGRAMMING_MODE;
    ProgramHandler::Init();
    break;

  case BLUE_SWITH_SPOT:
  default:
    globals::CurrentState = STATE_MUSIC_MODE;  
    DEBUGLN1( "Position: Middle (SPOT) CurrentState = STATE_MUSIC_MODE" ); // auch bei default
    break;  
  }
}

///
/// Read all interfaces and fill the depending variables. Some of the interfaces are not
/// read every loop, to get a better performance. For most of the variables it is OK to
/// check the state 4 Times per second
void FillAllGlobalStateVariables() 
{
  ShellButtons::UpdateState();
  UltrasonicReceiver::UpdateState();
  HeadPots::UpdateState();
  EyeSwitches::UpdateState(); 
  BlueSwitch::UpdateState();
}

///
/// Standard Arduino setup(). Called after power on Arduino
///
void setup() {

  globals::CurrentLoopTimeInMs = millis();
  
  DEBUG_INIT();

  // initialise the TWI bus
  Wire.begin();

  // show wakeup, one round of Leds
  // DotheNightRider();

  UltrasonicReceiver::Init();
  HeadPots::Init();
  EyeSwitches::Init();
  BlueSwitch::Init();
  ShellButtons::Init();
  
  // BlueSwitchInit();

  DEBUGLN1("*** Newromancy ready to make noise ***");
}

inline void ProcessHeadPods(int aPotLeftValue, int aPotRightValue) 
{
  
    short value = map( aPotLeftValue, 0, 127, -2, 2 );
    globals::TransposeOffset = value * 12;
    DEBUG(aPotLeftValue);DEBUG(" Transpose=");DEBUGLN1(globals::TransposeOffset);
}

inline void  ProcessEyeSwitches(bool aStateSwitchLeft, bool aStateSwitchRight) 
{
  if (aStateSwitchLeft) {
    ShellButtons::SendAllNotesOff();
  }
}

inline void ProcessStateChanges() {
  
  if (ShellButtons::IsStateChanged) {
    ShellButtons::IsStateChanged = false;    
    // DEBUGLN1("ShellButtons state changed!");
  }
  
  if (EyeSwitches::IsStateChanged) {
    EyeSwitches::IsStateChanged = false;
    DEBUG("EyeSwitches state changed!");
    DEBUG("Left:");
    DEBUG(EyeSwitches::StateSwitchLeft);
    DEBUG(" Right:");
    DEBUGLN1(EyeSwitches::StateSwitchRight);    
    ProcessEyeSwitches(EyeSwitches::StateSwitchLeft, EyeSwitches::StateSwitchRight);
  }
  
  if (HeadPots::IsStateChanged) {
    HeadPots::IsStateChanged = false;
    DEBUG("HeadPot state changed!");
    DEBUG("Left:");
    DEBUG(HeadPots::PotLeftValue);
    DEBUG(" Right:");
    DEBUGLN1(HeadPots::PotRightValue);    
    ProcessHeadPods(HeadPots::PotLeftValue, HeadPots::PotRightValue);    
  }
  
  if (UltrasonicReceiver::IsStateChanged) {
    UltrasonicReceiver::IsStateChanged = false;
    /*
    DEBUG("UltrasonicReceiver state changed!");
    DEBUG("Distance:");
    DEBUGLN1(UltrasonicReceiver::Distance);    
    */
  }
  
  if  (BlueSwitch::IsStateChanged) {
    BlueSwitch::IsStateChanged = false;
    DEBUG("BlueSwitch state changed!");
    switch( BlueSwitch::BlueSwitchState ) {
    case BLUE_SWITCH_NOTE:
      DEBUGLN1( "Position: Left (NOTE)" );
      break;
    case BLUE_SWITCH_FACE:
      DEBUGLN1( "Position: Right (FACE)" );
      break;
    case BLUE_SWITH_SPOT:
    default:
      DEBUGLN1( "Position: Middle (SPOT)" ); // auch bei default
      break;  
    }    
  }
  
}

///
/// Standard Arduino loop(). 
///
void loop() {

  
  globals::CurrentLoopTimeInMs = millis();
  // BlueSwitchHandleStateChange();

  FillAllGlobalStateVariables();
  ProcessStateChanges();
}

/*
  if (globals::CurrentState == STATE_MUSIC_MODE) {
 // kommt noch TODO
 } else {
 // STATE_PROGRAMMING_MODE
 ProgramHandler::HandleProgrammSetup();
 }*/











